package com.jichangxiu.framework.config;

import cn.hutool.core.util.StrUtil;
import com.jichangxiu.common.properties.JcxProperties;
import com.jichangxiu.common.utils.TokenUtils;
import com.jichangxiu.framework.filter.CaptchaFilter;
import com.jichangxiu.framework.security.encoder.PasswordEncoder;
import com.jichangxiu.framework.security.filter.AuthFilter;
import com.jichangxiu.framework.security.filter.LoginFilter;
import com.jichangxiu.framework.security.handler.LogoutHandler;
import com.jichangxiu.framework.security.point.AuthEntryPoint;
import com.jichangxiu.framework.security.provider.AuthProvider;
import com.jichangxiu.framework.security.service.AuthUserDetailsService;
import com.jichangxiu.framework.service.LoginInfoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * SecurityConfig:
 *
 * @author Jcx
 * @create 2023-04-04 13:25
 */
@Slf4j
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig {

    @Resource
    private JcxProperties jcxProperties;

    @Resource
    private CaptchaFilter captchaFilter;

    @Resource
    private AuthFilter authFilter;

    @Resource
    private AuthEntryPoint authEntryPoint;

    @Resource
    private LogoutHandler logoutHandler;

    @Resource
    private AuthUserDetailsService authUserDetailsService;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @Resource
    private TokenUtils tokenUtils;

    private LoginInfoService loginInfoService;

    @Autowired
    public void setLoginInfoService(LoginInfoService loginInfoService) {
        this.loginInfoService = loginInfoService;
    }

    public LoginFilter jcxLoginFilter() {
        log.info("加载 jcxLoginFilter");
        LoginFilter loginFilter = new LoginFilter(loginInfoService, jcxProperties, tokenUtils);
        loginFilter.setAuthenticationManager(authenticationManager());
        loginFilter.setFilterProcessesUrl(jcxProperties.getLoginProperties().getLoginProcessingUrl());
        return loginFilter;
    }

    public AuthProvider jcxAuthProvider() {
        log.info("加载 jcxAuthProvider");
        return new AuthProvider(authUserDetailsService, passwordEncoder);
    }

    public AuthenticationManager authenticationManager() {
        log.info("加载 AuthenticationManager");
        List<AuthenticationProvider> providerList = new ArrayList<>();
        providerList.add(jcxAuthProvider());
        ProviderManager providerManager = new ProviderManager(providerList);
        providerManager.setAuthenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher));
        return providerManager;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        log.info("加载 SecurityFilterChain");
        String noAuthPath = jcxProperties.getAuthProperties().getNoAuthPath();
        String noAuthStaticPath = jcxProperties.getAuthProperties().getNoAuthStaticPath();
        // 关闭跨站
        http.cors().and().csrf().disable();
        // 登录配置
        http.httpBasic().and().formLogin().loginProcessingUrl(jcxProperties.getLoginProperties().getLoginProcessingUrl())
                .and().logout().logoutUrl(jcxProperties.getLoginProperties().getLogoutProcessingUrl())
                .logoutSuccessHandler(logoutHandler);
        // 禁用 session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 配置拦截规则
        http.authorizeRequests()
                // 放行预检请求
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                // 允许匿名访问
                .antMatchers(StrUtil.isNotEmpty(noAuthPath) ? noAuthPath.split(",") : new String[]{}).permitAll()
                // 放行静态资源
                .antMatchers(HttpMethod.GET, StrUtil.isNotEmpty(noAuthStaticPath) ? noAuthStaticPath.split(",") : new String[]{}).permitAll()
                // 其他请求都需要经过验证
                .anyRequest().authenticated();
        // 异常处理器
        http.exceptionHandling().authenticationEntryPoint(authEntryPoint);
        // 配置自定义的过滤器
        http.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class);
        // 验证码过滤器放在 UsernamePassword 过滤器之前
        http.addFilterBefore(captchaFilter, AbstractPreAuthenticatedProcessingFilter.class);
        // 自定义登陆方式
        http.addFilterAfter(jcxLoginFilter(), CaptchaFilter.class);
        return http.build();
    }

}
